const WebTorrent = require("webtorrent");
const path = require("path");
const util = require("util");
const Queue = require("promise-queue");
const Camo = require("camo");
const CamoDoc = Camo.Document;
const events = require("events");
const filesize = require("filesize")

class Task extends CamoDoc {
  constructor() {
    super();
    this.torrentId = String;
    this.torrentStoreDir = String;
    this.extraInfo = Object;
  }

  static collectionName() {
    return "HujimiyaWebtorrentTask";
  }
}

/**
 *
 * @class Downloader
 * @extends {events.EventEmitter}
 */
class Downloader extends events.EventEmitter {
  /**
   *Creates an instance of Downloader.
   * @param {String} camo_store_path
   * @param {Number} download_queue_cap
   * @memberof Downloader
   */
  constructor(camo_store_path, download_queue_cap = 5) {
    super();
    this.client = new WebTorrent;
    this.queue = new Queue(download_queue_cap, Infinity);
    this.dbconnected = Camo.connect(`nedb://${camo_store_path}`);
    this.dbconnected.then($ => {
      Task.find({}).then(tasks => {
        tasks.forEach(t => {
          this.__addToDownloadQueue(t);
        })
      })
    });
    this.on("task-delete",
      /**
       * @param {String} torrentId
       */
      (torrentId) => {
        Task.deleteOne({
          torrentId: torrentId
        }).then(() => {
          console.log("wrap-torrent 删除了任务", torrentId)
        })
      })
  }


  /**
   *
   * @param {Task} task
   * @param {{torrentId:String,torrentStoreDir:String,extraInfo:Object}} task_real
   * @memberof Downloader
   */
  __addToDownloadQueue(task) {
    let task_doc = task;
    let _this = this;
    let download_function = () => new Promise(torrent_resolve => {
      let torrent = _this.client.add(task_doc.torrentId, {
        path: task_doc.torrentStoreDir
      });
      let metaFlag = false;
      let download_speeds = [];
      let upload_speeds = []
      torrent.on("metadata", () => {
        metaFlag = true;
        let containsIllegalCharacterFiles = torrent.files.filter(f => f.path.includes("|") || f.path.includes(">") || f.path.includes("*"));
        if (containsIllegalCharacterFiles.length >= 1) {
          torrent_resolve();
          _this.emit("task-fail", {
            task: task_doc,
            code: "ILLEGAL_CHARACTER",
            reason: `文件列表中含有"|*>"字符不能正常下载:${containsIllegalCharacterFiles.map(f => f.path).join(" , ")}`
          })
          task.delete();
          torrent.destroy();
          return;
        }
        let it = setInterval(() => {
          download_speeds.push(torrent.downloadSpeed);
          upload_speeds.push(torrent.uploadSpeed);
          (debug_log => {
            return;
            let down = download_speeds.slice(-30);
            let up = upload_speeds.slice(-30);

            console.log(task.torrentId, down.map((d, i) => `[D:${filesize(d)} U:${filesize(up[i])}]`).join(","))
          })()

          if (torrent.destroyed || torrent.done) {
            //因为destory以后仍然会有download速度记录 所以destroy以后应该把这个去掉
            return clearInterval(it);
          }
          if (download_speeds.length > 60 * 2) {
            download_speeds.shift();
            upload_speeds.shift();
            if ((!download_speeds.find(s => s > 10)) &&
              (!upload_speeds.find(s => s > 10))) {
              torrent_resolve();
              _this.emit("task-fail", {
                task: task_doc,
                code: "LOW_SPEED",
                reason: `过去2分钟内记录到的下载和上传速度极低:${download_speeds.map(
                  (s, i) => `[down:${filesize(s) + "/s"},up:${filesize(upload_speeds[i]) + "/s"}]`
                ).join(",")}`
              })
              task.delete();
              torrent.destroy();
            }
          }
        }, 1000);

        let watchSpeedIt = setInterval($ => {
          if ((!torrent.done) && (!torrent.destroyed)) {
            _this.emit("task-speed", {
              task: task_doc,
              code: "WATCH SPEEED",
              reason: `|${download_speeds.map(
                (s, i) => `[down:${filesize(s) + "/s"},up:${filesize(upload_speeds[i]) + "/s"}]`
              ).join(",")}|`
            });
          } else {
            clearInterval(watchSpeedIt);
          }
        }, 5000);

      });
      torrent.on("ready", () => {
        // debugger
      })
      setTimeout($ => {
        if (!metaFlag && !torrent.destroyed) {
          torrent_resolve();
          /**
           * @event Downloader#task-fail 
           * @type {Object}
           * @property {typeof task_doc} task
           * @property {String} code
           * @property {String} reason
           */
          _this.emit("task-fail", {
            task: task_doc,
            code: "NO-SEEDER",
            reason: `NO METADATA after 2 minutes`
          });
          _this.emit("task-speed", {
            task: task_doc,
            code: "WATCH SPEEED",
            reason: `|${download_speeds.map(
              (s, i) => `[down:${filesize(s) + "/s"},up:${filesize(upload_speeds[i]) + "/s"}]`
            ).slice(-10).join(",")}|`
          });
          task.delete();
        }
      }, 60 * 2 * 1000);
      torrent.on("error", (err) => {
        if (err.message.includes("Cannot add duplicate torrent")) {
          torrent_resolve();
          _this.emit("task-duplicate", {
            task: task_doc,
            code: "DUPLICATE",
            reason: err.message
          });
          task.delete();
        } else {
          torrent_resolve();
          _this.emit("task-fail", {
            task: task_doc,
            code: "ERROR",
            reason: err.message
          });
          task.delete();
        }
        torrent.destroy();
      });
      torrent.on("done", () => {
        torrent_resolve();
        _this.emit("task-done", {
          task: task_doc,
          code: "OK",
          reason: `OK`
        });
        _this.emit("task-speed", {
          task: task_doc,
          code: "WATCH SPEEED",
          reason: `|${download_speeds.map(
            (s, i) => `[down:${filesize(s) + "/s"},up:${filesize(upload_speeds[i]) + "/s"}]`
          ).join(",")}|`
        });
        // task.delete();
        torrent.destroy();
      });
    })
    _this.queue.add(download_function);
  }

  /**
   *
   * @returns {Promise<{ok:Boolean,msg:String,tasks:[]}>}
   * @param {String} torent_id
   * @memberof Downloader
   */
  __findTasksByTorrentIdAfterDbconnected(torent_id) {
    return new Promise(resolve => {
      Task.find({
        torrentId: torent_id
      }).then(docs => {
        resolve({
          ok: true,
          msg: "ok",
          tasks: docs
        })
      }).catch(err => {
        resolve({
          ok: false,
          msg: `error when Task.find:${err}`,
          tasks: []
        })
      })
    })
  }

  /**
   * @returns {Promise<{added_to_queue:Boolean,msg:String}>}
   * @param {String} torrentId 
   * @param {String} torrentStoreDir 
   * @param {Object} extra_info
   */
  addTask(torrentId, torrentStoreDir, extra_info) {
    let _this = this;
    return new Promise(resolve => {
      this.dbconnected.then(async () => {
        let task_doc = Task.create({
          torrentId,
          torrentStoreDir,
          extraInfo: extra_info
        });
        let o_find_exist = await this.__findTasksByTorrentIdAfterDbconnected(torrentId);
        if (o_find_exist.ok && o_find_exist.tasks.length > 0) {
          return resolve({
            added_to_queue: true,
            msg: "already in database"
          })
        }

        let saved = task_doc.save();
        saved.then(doc => {
          _this.__addToDownloadQueue(task_doc)
          resolve({
            added_to_queue: true,
            msg: "ok"
          })
        });
        saved.catch(err => {
          console.error(err);
          resolve({
            added_to_queue: false,
            msg: `error when task_doc.save:${err}`
          })
        });
      });
      this.dbconnected.catch(err => {
        resolve({
          added_to_queue: false,
          msg: `error when dbconnected:${err}`
        })
      })
    })
  }


  /**
   *
   *
   * @returns {Promise<{ok:Boolean,msg:String,count:Number}>}
   * @memberof Downloader
   */
  getDbTaskCount() {
    return new Promise(resolve => {
      this.dbconnected.then($ => {
        Task.find({}).then(tasks => {
          resolve({
            ok: true,
            msg: "ok",
            count: tasks.length
          })
        }).catch(err => {
          resolve({
            ok: false,
            msg: "Task.find error" + e,
            count: 0
          })
        })
      }).catch(e => {
        resolve({
          ok: false,
          msg: "dbconnected error" + e,
          count: 0
        })
      })
    })
  }
}


module.exports = {
  Downloader
}